/****************************************************************************
 * Copyright (c) 2004, 2007 Composent, Inc., Peter Nehrer, Boris Bokowski.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Composent, Inc. - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package org.eclipse.ecf.provider.datashare;

import java.io.Serializable;
import java.util.Map;
import org.eclipse.ecf.core.events.IContainerConnectedEvent;
import org.eclipse.ecf.core.events.IContainerDisconnectedEvent;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.sharedobject.*;
import org.eclipse.ecf.core.sharedobject.events.ISharedObjectMessageEvent;
import org.eclipse.ecf.core.util.*;
import org.eclipse.ecf.datashare.*;
import org.eclipse.ecf.datashare.events.*;
import org.eclipse.ecf.internal.provider.datashare.Activator;

public class BaseChannel extends TransactionSharedObject implements IChannel {

	public static final String RECEIVER_ID_PROPERTY = BaseChannel.class.getName();

	static class ChannelMsg implements Serializable {
		private static final long serialVersionUID = 9065358269778864152L;
		byte[] channelData = null;

		ChannelMsg() {
			//
		}

		ChannelMsg(byte[] data) {
			this.channelData = data;
		}

		byte[] getData() {
			return channelData;
		}

		public String toString() {
			StringBuffer buf = new StringBuffer("BaseChannel.ChannelMsg["); //$NON-NLS-1$
			buf.append("data=").append(getData()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
			return buf.toString();
		}
	}

	protected IChannelListener listener;

	/**
	 * Primary copy implementation of channel class constructor
	 * 
	 * @param config
	 *            the ISharedObjectTransactionConfig associated with this new
	 *            host instance
	 * @param listener
	 *            the listener associated with this channel instance
	 */
	public BaseChannel(ISharedObjectTransactionConfig config, IChannelListener listener) {
		super(config);
		setChannelListener(listener);
	}

	/**
	 * Replica implementation of channel class constructor
	 * 
	 */
	public BaseChannel() {
		super();
	}

	protected void setChannelListener(IChannelListener l) {
		this.listener = l;
	}

	protected void trace(String msg) {
		Trace.trace(Activator.PLUGIN_ID, msg);
	}

	/**
	 * Override of TransasctionSharedObject.initialize(). This method is called
	 * on both the host and the replicas during initialization. <b>Subclasses
	 * that override this method should be certain to call super.initialize() as
	 * the first thing in their own initialization so they get the
	 * initialization defined by TransactionSharedObject and BaseSharedObject.</b>
	 * 
	 * @throws SharedObjectInitException
	 *             if initialization should fail
	 */
	protected void initialize() throws SharedObjectInitException {
		super.initialize();

		if (!isPrimary())
			initializeReplicaChannel();

		addEventProcessor(new IEventProcessor() {
			public boolean processEvent(Event event) {
				trace("processEvent(" + event + ")"); //$NON-NLS-1$ //$NON-NLS-2$
				IChannelListener l = getListener();
				if (event instanceof IContainerConnectedEvent) {
					if (l != null)
						l.handleChannelEvent(createChannelGroupJoinEvent(true, ((IContainerConnectedEvent) event).getTargetID()));
				} else if (event instanceof IContainerDisconnectedEvent) {
					if (l != null)
						l.handleChannelEvent(createChannelGroupDepartEvent(true, ((IContainerDisconnectedEvent) event).getTargetID()));
				} else if (event instanceof ISharedObjectMessageEvent) {
					BaseChannel.this.handleMessageEvent((ISharedObjectMessageEvent) event);
				}
				return false;
			}
		});
		trace("initialize()"); //$NON-NLS-1$

	}

	/**
	 * Override of TransactionSharedObject.getAdapter()
	 */
	public Object getAdapter(Class clazz) {
		if (clazz.equals(IChannel.class)) {
			return this;
		}
		return super.getAdapter(clazz);
	}

	IChannelConnectEvent createChannelGroupJoinEvent(final boolean hasJoined, final ID targetID) {
		return new IChannelConnectEvent() {
			public ID getTargetID() {
				return targetID;
			}

			public ID getChannelID() {
				return getID();
			}

			public String toString() {
				StringBuffer buf = new StringBuffer("ChannelConnectEvent["); //$NON-NLS-1$
				buf.append("channelid=").append(getChannelID()).append(";targetid=").append(getTargetID()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				return buf.toString();
			}
		};
	}

	IChannelDisconnectEvent createChannelGroupDepartEvent(final boolean hasJoined, final ID targetID) {
		return new IChannelDisconnectEvent() {
			public ID getTargetID() {
				return targetID;
			}

			public ID getChannelID() {
				return getID();
			}

			public String toString() {
				StringBuffer buf = new StringBuffer("ChannelDisconnectEvent["); //$NON-NLS-1$
				buf.append("channelid=").append(getChannelID()).append(";targetid=").append(getTargetID()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				return buf.toString();
			}
		};
	}

	Event handleMessageEvent(final ISharedObjectMessageEvent event) {
		Object eventData = event.getData();
		ChannelMsg channelMsg = null;
		IChannelListener l = getListener();
		if (eventData instanceof ChannelMsg) {
			channelMsg = (ChannelMsg) eventData;
			final byte[] channelData = channelMsg.getData();
			if (channelData != null) {
				if (l == null)
					return event;
				listener.handleChannelEvent(new IChannelMessageEvent() {
					public ID getFromContainerID() {
						return event.getRemoteContainerID();
					}

					public byte[] getData() {
						return channelData;
					}

					public ID getChannelID() {
						return getID();
					}

					public String toString() {
						StringBuffer buf = new StringBuffer("ChannelMessageEvent["); //$NON-NLS-1$
						buf.append("channelid=").append(getChannelID()).append(";fromid=").append(getFromContainerID()).append(";data=").append(getData()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
						return buf.toString();
					}
				});
				// Discontinue processing of this event...we are it
				return null;
			}
		}
		return event;
	}

	// Implementation of org.eclipse.ecf.datashare.IChannel
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.datashare.IChannel#sendMessage(byte[])
	 */
	public void sendMessage(byte[] message) throws ECFException {
		sendMessage(null, message);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.datashare.IChannel#sendMessage(org.eclipse.ecf.core.identity.ID,
	 *      byte[])
	 */
	public void sendMessage(ID receiver, byte[] message) throws ECFException {
		try {
			getContext().sendMessage(receiver, new ChannelMsg(message));
		} catch (Exception e) {
			throw new ECFException("send message exception", e); //$NON-NLS-1$
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.datashare.IAbstractChannel#getListener()
	 */
	public synchronized IChannelListener getListener() {
		return listener;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.datashare.IAbstractChannel#setListener(org.eclipse.ecf.datashare.IChannelListener)
	 */
	public IChannelListener setListener(IChannelListener listener) {
		IChannelListener oldListener = getListener();
		setChannelListener(listener);
		return oldListener;
	}

	// Subclass API
	/**
	 * Receive and process channel events. This method can be overridden by
	 * subclasses to process channel events in a sub-class specific manner.
	 * 
	 * @param channelEvent
	 *            the IChannelEvent to receive and process
	 */
	protected void receiveUndeliveredChannelEvent(IChannelEvent channelEvent) {
		if (isPrimary())
			trace("host.receiveUndeliveredChannelEvent(" + channelEvent + ";containerid=" + getContext().getLocalContainerID() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		else
			trace("replica.receiveUndeliveredChannelEvent(" + channelEvent + ";containerid=" + getContext().getLocalContainerID() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}

	/**
	 * Override of BaseSharedObject.getReplicaDescription. Note this method
	 * should be overridden by subclasses that wish to specify the type of the
	 * replica created.
	 * 
	 * @param targetContainerID
	 *            the ID of the target container for subsequentreplica creation.
	 *            If null, the target is <b>all</b> current group members
	 * @return ReplicaSharedObjectDescripton to be used for creating remote
	 *         replica of this host shared object. If null, no create message
	 *         will be sent to the target container.
	 */
	protected ReplicaSharedObjectDescription getReplicaDescription(ID targetContainerID) {
		return new ReplicaSharedObjectDescription(getClass(), getID(), getConfig().getHomeContainerID(), getConfig().getProperties());
	}

	/**
	 * Initialize replicas of this channel. This method is only called if
	 * isPrimary() returns false. It is called from within the initialize
	 * method, immediately after super.initialize but before the listener for
	 * this channel is notified of initialization. If this method throws a
	 * SharedObjectInitException, then initialization of the replica is halted
	 * and the remote transaction creating the replica will be aborted.
	 * <p>
	 * Note that this implementation checks for the existence of the
	 * RECEIVER_ID_PROPERTY on the replica's properties, and if the property
	 * contains a valid ID will
	 * <ul>
	 * <li>lookup the IChannel on the given container via
	 * IChannelContainerAdapter.getID(ID)</li>
	 * <li>call IChannel.getListener() to retrieve the listener for the channel
	 * returned</li>
	 * <li>set the listener for this object to the value returned from
	 * IChannel.getListener()</li>
	 * </ul>
	 * 
	 * @throws SharedObjectInitException
	 *             if the replica initialization should fail
	 */
	protected void initializeReplicaChannel() throws SharedObjectInitException {
		Map properties = getConfig().getProperties();
		ID rcvr = null;
		try {
			rcvr = (ID) properties.get(RECEIVER_ID_PROPERTY);
		} catch (ClassCastException e) {
			throw new SharedObjectInitException("Receiver ID property value must be of type ID", e); //$NON-NLS-1$
		}
		if (rcvr != null) {
			// Now...get local channel container first...throw if we can't get
			// it
			IChannelContainerAdapter container = (IChannelContainerAdapter) getContext().getAdapter(IChannelContainerAdapter.class);
			if (container == null)
				throw new SharedObjectInitException("channel container adapter must not be null"); //$NON-NLS-1$
			// Now get receiver IChannel...throw if we can't get it
			final IChannel receiver = container.getChannel(rcvr);
			if (receiver == null)
				throw new SharedObjectInitException("channel receiver must not be null"); //$NON-NLS-1$
			setChannelListener(receiver.getListener());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.datashare.IAbstractChannel#dispose()
	 */
	public void dispose() {
		destroySelfLocal();
	}
}
